package middleware

import (
	"bytes"
	"io"
	"log/slog"
	"time"

	"github.com/gin-gonic/gin"

	"{{filepathJoin .ModName "internal/pkg/md5"}}"
	"{{filepathJoin .ModName "internal/pkg/uuid"}}"
)

// RequestLogMiddleware 返回一个 gin.HandlerFunc，用于为每个 HTTP 请求生成日志。
// 该中间件主要记录请求的唯一标识、方法、URL、参数等信息，有助于请求跟踪和日志审计。
func RequestLogMiddleware() gin.HandlerFunc {
	// 返回一个闭包函数，作为 HTTP 请求的处理函数
	return func(ctx *gin.Context) {
		// 生成请求的唯一标识，用于跟踪请求
		// The configuration is initialized once per request
		trace := md5.Md5(uuid.GenUUID())

		// 尝试获取请求体的内容，用于记录请求参数
		var bodyBytes []byte
		if ctx.Request.Body != nil {
			bodyBytes, _ = ctx.GetRawData()
			// 重新包装请求体，确保其可以被多次读取
			ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // 关键点
		}

		// 构建日志记录的参数，包括请求的唯一标识、方法、URL、请求头和请求体内容
		args := []any{
			slog.String("trace", trace),
			slog.String("request_method", ctx.Request.Method),
			slog.Any("request_headers", ctx.Request.Header),
			slog.String("request_url", ctx.Request.URL.String()),
			slog.String("request_params", string(bodyBytes)),
		}

		// 将请求的唯一标识存储在上下文中，以便后续使用
		ctx.Set("trace", trace)
		// 记录请求开始的日志
		slog.DebugContext(ctx, "Request", args...)
		// 继续处理后续的中间件和处理器
		ctx.Next()
	}
}

// ResponseLogMiddleware 返回一个Gin中间件，用于记录HTTP响应的时间、状态、头信息和正文。
// 该中间件在请求处理链中插入一个自定义的response writer，用于捕获并记录响应细节。
func ResponseLogMiddleware() gin.HandlerFunc {
	// 返回一个闭包函数作为Gin中间件
	return func(ctx *gin.Context) {
		// 初始化一个自定义的response writer，用于捕获响应内容和状态
		blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer}
		// 使用自定义的response writer替换默认的，以便后续捕获响应信息
		ctx.Writer = blw

		// 记录当前时间，用于计算请求处理时间
		startTime := time.Now()
		// 继续处理请求，调用下一个中间件或处理器
		ctx.Next()

		// 计算请求处理时间
		duration := time.Since(startTime).String()
		// 设置响应头，携带请求处理时间
		ctx.Header("X-Response-Time", duration)

		// 从上下文中获取trace信息，用于日志标识
		trace, _ := ctx.Get("trace")
		// 准备日志记录的参数
		args := []any{
			slog.String("trace", trace.(string)),
			slog.Int("response_status", ctx.Writer.Status()),
			slog.Any("response_headers", ctx.Writer.Header()),
			slog.String("response_body", blw.body.String()),
			slog.String("time", duration),
		}

		// 使用slog记录请求的响应信息
		slog.DebugContext(ctx, "Response", args...)
	}
}

// bodyLogWriter 是一个实现了 io.Writer 接口的类型，用于在写入响应体的同时记录写入的数据。
// 它通过嵌入 http.ResponseWriter 来间接地向客户端发送数据，并在写入过程中对数据进行监控或处理。
type bodyLogWriter struct {
	body               *bytes.Buffer // body 用于存储写入的字节数据，以便后续处理或记录。
	gin.ResponseWriter               // ResponseWriter 是用于向客户端发送数据的接口。
}

// Write 方法实现了 io.Writer 接口，它将数据首先写入 body，然后通过 ResponseWriter 将相同的数据写入到 HTTP 响应中。
// 这种设计允许在不改变原有写入逻辑的情况下，对写入的数据进行额外的操作，如日志记录、数据转换等。
// 参数 b 是待写入的字节切片。
// 返回值是写入的字节大小和可能发生的错误。
func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}
